﻿using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;

namespace SQLLink.DomainClasses
{
    /// <summary>
    /// The Class BusLine contains fields and methods applicable to a busLine object
    /// </summary>
    public class BusLine
    {
        //The fields that are going to be mapped on the SQL Database
        public         int       Id     { get; set; } //The id of the BusLine
        public         int       Number { get; set; } //The number of the busLine
        public virtual List<Bus> Busses { get; set; } //The busLine's list of bus

        //The fields that are NOT going to be mapped on the SQL Database
        [NotMapped] public List<GPRMC>  GPRMCs   { get; set; } //The buslines GPRMCs that a bus can have
        [NotMapped] public List<Marker> Markers  { get; set; } //All of the Markers contained by the BusLine
        [NotMapped] public List<int>    Indexes  { get; set; } //The list of indexes 
        [NotMapped] public string LocalizerPath  { get; set; } //The path to the file containing the Busline's list of markers
        
        /// <summary>
        /// BusLine
        /// This is a BusLine Object Empty Constructor
        /// </summary>
        public BusLine()
        {
            Markers = new List<Marker>();
        }

        /// <summary>
        /// BusLine
        /// This is a specific constructor for busLine objects. It is created with its list of markers
        /// </summary>
        /// <param name="iMarkers">A list of Markers</param>
        public BusLine(List<Marker> iMarkers)
        {
            Markers = iMarkers;
        }

        /// <summary>
        /// SortMarkers()
        /// This method is used to sort the Markers. 
        /// It computes every Marker's DistanceToDestination and DistanceToArrival
        /// and sorts the Markers with a Comparer.
        /// </summary>
        public void SortMarkers()
        {
            ComputeMarkerDistances();
            Markers.Sort(Compare);
        }

        /// <summary>
        /// ComputeMarkerDistances()
        /// This method is used to compute Markers' DistanceToDestination and DistanceToArrival.
        /// </summary>
        private void ComputeMarkerDistances()
        {
            foreach (var marker in Markers)
            {
                marker.ComputeDistanceFromDeparture(GetMarker(MarkerType.departure));
                marker.ComputeDistanceToDestination(GetMarker(MarkerType.arrival));
            }
        }

        /// <summary>
        /// GetMarker
        /// This method finds a Marker of a specific type in the Markers
        /// In case it doesn't find any of the wanted type, it returns the owner Marker
        /// </summary>
        /// <param name="markerType">The type of the wanted Marker</param>
        /// <returns>The wanted Marker</returns>
        private Marker GetMarker(MarkerType markerType)
        {
            return Markers.Find(x => x.Type == markerType) ?? Markers.Find(x => x.Type == MarkerType.owner);
        }

        /// <summary>
        /// GetAllMarkers(MarkerType)
        /// This method finds all Markers of a specific type in the Markers
        /// </summary>
        /// <param name="markerType">The type of the wanted Marker</param>
        /// <returns>The wanted Marker</returns>
        private IEnumerable<Marker> GetAllMarkers(MarkerType markerType)
        {
            return Markers.FindAll(x => x.Type == markerType);
        }

        /// <summary>
        /// Compare
        /// This is a Marker Comparator. 
        /// It allows to sort the busLine's list of markers according to their ascending Id.
        /// </summary>
        /// <param name="x">First Marker</param>
        /// <param name="y">Second Marker</param>
        /// <returns>1 if x should be after y, 0 if the markers are equal, -1 if y should be after x</returns>
        public int Compare(Marker x, Marker y)
        {
            //If x is a departure...
            if (MarkerType.departure == x.Type)
                //If y is a departure, x=y, else x<y
                return MarkerType.departure == y.Type ? 0 : -1;

            //x is not a departure. If y is a departure...
            if (MarkerType.departure == y.Type)
                return 1; //x>y

            //None of the are departure Markers. If x is an arrival...
            if (MarkerType.arrival == x.Type)
                //If y is an arrival, x=y, else x>y
                return MarkerType.arrival == y.Type ? 0 : 1;

            //x is not an arrival. If y is an arrival
            if (MarkerType.arrival == y.Type)
                return -1; //x<y

            if (x.Id < y.Id)
                return -1;

            return x.Id > y.Id ? 1 : 0;
        }

        /// <summary>
        /// This method uses the Markers busses to create a google format string
        /// </summary>
        /// <returns>A string in the google request format</returns>
        public string ComingBussesToString()
        {
            var googleAPIString = "";

            // Retrieves all the busses from the markers
            var busses = GetAllMarkers(MarkerType.bus);

            foreach (var bus in busses)
            {
                // We will not ask for distance and duration data if the bus has passed the owner marker
                if (!bus.HasPassed)
                {
                    // Separates busses' latlons with '|'
                    googleAPIString += bus.LatLon + "|";
                }
            }
            
            // Returns the string removing the last '|'
            return googleAPIString.Remove(googleAPIString.Count() - 1);
        }

        /// <summary>
        /// This method returns the number of busses that haven't passed yet
        /// </summary>
        /// <returns>The number of coming busses</returns>
        public int GetNumberOfComingBusses()
        {
            return GetComingBusses().Count;
        }

        public List<Marker> GetComingBusses()
        {
            return Markers.FindAll(x => x.Type == MarkerType.bus && !x.HasPassed);
        }

        /// <summary>
        /// This method return the owner Marker's LatLon object
        /// </summary>
        /// <returns>The owner's LatLon object</returns>
        public LatLon GetOwnerLatLon()
        {
            return GetMarker(MarkerType.owner).LatLon;
        }

        /// <summary>
        /// SetOwner
        /// This method allows to set the owner marker in the busLine's list of markers
        /// </summary>
        /// <param name="owner">The marker to set as the busLine owner</param>
        public void SetOwner(Marker owner)
        {
            // Remove owner state from previous
            var oldOwner = GetMarker(MarkerType.owner);

            // Sorts the markers according to ther Ids
            SortMarkers();

            // The first should be the departure marker
            if(oldOwner == GetMarker(MarkerType.departure))
            {
                Markers[Markers.IndexOf(oldOwner)].SetType(MarkerType.departure);
            }
            // The last should be the arrival marker
            else if (oldOwner == GetMarker(MarkerType.arrival))
            {
                Markers[Markers.IndexOf(oldOwner)].SetType(MarkerType.arrival);
            }
            // Any other marker case
            else if(oldOwner != null)
            {
                Markers[Markers.IndexOf(oldOwner)].SetType(MarkerType.other);
            }

            // Set the new owner
            var newOwner = Markers.Find(m => m.Id == owner.Id);
            if (newOwner != null)
            {
                Markers[Markers.IndexOf(newOwner)].SetType(MarkerType.owner);
            }
        }

        /// <summary>
        /// SetTypeAccordingToOrder
        /// This method sets the MarkerType of the busLine's list of markers according to their position in the list.
        /// </summary>
        public void SetTypeAccordingToOrder()
        {
            if (Markers.Any())
            {
                Markers.First().SetType(MarkerType.departure);
                Markers.Last().SetType(MarkerType.arrival);

                foreach (var marker in Markers)
                {
                    if (marker.Type == MarkerType.none)
                        marker.SetType(MarkerType.other);
                }
            }
        }

        /// <summary>
        /// RefreshBusses
        /// This method is used to refresh the busLine's busses in its list of markers
        /// </summary>
        public void RefreshBusses()
        {
            //Gets the number of markers in Markers
            var nextId = Markers.Count;

            //Removes the busses from the Markers (because they changed location)
            Markers.RemoveAll(x => x.Type == MarkerType.bus);
            
            //Creates a new list of GPRMCs for the busses
            var bussesGPRMCs = new List<GPRMC>();

            //Iterates on the local busses
            foreach (var bus in Busses)
            {
                if (bus.GPRMCs.Count > 0)
                {
                    //Adds its last GPRMC to the list of GPRMCs for the busses
                    bussesGPRMCs.Add(bus.GPRMCs.Last());
                }
            }

            //Iterates on the bussesGRPMCs just added
            foreach (var GPRMC in bussesGPRMCs)
            {
                //Adds the new busses marker with they latlon, name, id and type
                Markers.Add(new Marker(new LatLon(GPRMC), nextId.ToString(), nextId++, MarkerType.bus));
            }

            //Sorts the whole marker list
            SortMarkers();

            //Computes if the busses must be or not displayed
            ComputeHasPassed();
        }

        /// <summary>
        /// ComputeHasPassed
        /// This methods computes for every bus from a busLine's list of markers if it has 
        /// passed the owner marker. It starts by finding the two closest markers to every 
        /// bus (marker1 and marker2), and manipulates them in order to know their exact 
        /// relative position from every bus, then sets their property HasPassed.
        /// </summary>
        public void ComputeHasPassed()
        {
            //Gets all the bus markers
            var busses = GetAllMarkers(MarkerType.bus).ToList();
            
            //Gets all the other markers
            var others = Markers.Except(GetAllMarkers(MarkerType.bus)).ToList();

            //Iterates over the busses
            foreach (var bus in busses)
            {
                //Gets the index of the bus
                var busIndex = Markers.IndexOf(bus);

                //Gets the first two markers of the busline, 
                //and sets their distanceToNearestBus property to the maximum unsigned int value
                var marker1 = others[0];
                var marker2 = others[1];
                marker1.DistanceToNearestBus = uint.MaxValue;
                marker2.DistanceToNearestBus = uint.MaxValue;

                //Iterates over the other markers to find the bus' two nearest markers
                foreach (var other in others)
                {
                    if (marker1.DistanceToNearestBus > bus.ComputeDistanceToMarker(other))
                    {
                        //marker1 is set to other, because it is the at least the closest so far to the bus
                        marker1 = other;

                        //Sets the new distanceToNearestBus property for marker1
                        marker1.DistanceToNearestBus = bus.ComputeDistanceToMarker(marker1);
                    }
                }

                foreach(var other in others.FindAll(x => x.Id != marker1.Id))
                {
                    if (marker2.DistanceToNearestBus > bus.ComputeDistanceToMarker(other) && other != marker1)
                    {
                        //marker2 is set to the other, because it is at least the second closest so far to the bus
                        marker2 = other;

                        //Set the new distanceToNearestBus property for marker2
                        marker2.DistanceToNearestBus = bus.ComputeDistanceToMarker(marker2);
                    }
                }

                //This assures that marker1 is the nearest, and that marker2 is the second nearest
                if (marker1.DistanceToNearestBus > marker2.DistanceToNearestBus)
                {
                    var temp = marker1;
                    marker1 = marker2;
                    marker2 = temp;
                }

                //Creates another marker, which is the one right after marker1
                var nextMarkerRelativeToMarker1 = marker1.Id + 1 < others.Count ? others[marker1.Id + 1] : null;

                //If nextMarkerRelativeToMarker1 is null, then marker1 is the arrival marker
                if(nextMarkerRelativeToMarker1 == null)
                {
                    // 2---1---B OR 2---B---1
                    if(bus.ComputeDistanceToMarker(marker2) < marker1.ComputeDistanceToMarker(marker2))
                    {
                        // 2---B---1  
                        //Inverts the marker in order to put marker1 behind marker2
                        var temp = marker1;
                        marker1 = marker2;
                        marker2 = temp;
                    }
                    else
                    {
                        // 2---1---B
                        Markers[busIndex].HasPassed = true;
                        continue;
                    }
                }

                // If marker1 is behind the bus
                else if (marker1.ComputeDistanceToMarker(nextMarkerRelativeToMarker1) > bus.ComputeDistanceToMarker(nextMarkerRelativeToMarker1))
                {
                    // 1---B---N1
                    // If marker2 is behind the bus
                    if (bus.ComputeDistanceToMarker(nextMarkerRelativeToMarker1) < marker2.ComputeDistanceToMarker(nextMarkerRelativeToMarker1))
                    {
                        //  2---1---B---N1
                        marker2 = nextMarkerRelativeToMarker1;
                        // P1---1---B---2
                    }
                }

                // The bus is behind marker1
                // B---1
                // If the bus is behind marker2
                else if (marker2 == nextMarkerRelativeToMarker1)
                {
                    // B---1---2
                    marker2 = marker1;
                    //marker1 = others.ElementAt(marker1.Id - 1);
                    // 1---B---2
                }

                // The bus is in front of marker=2
                // 2---B---1
                else
                {
                    //Inverts the marker in order to put marker1 behind marker2
                    var temp = marker1;
                    marker1 = marker2;
                    marker2 = temp;
                }

                //Gets every markers that Ids are bigger than marker2's Id
                var nextMarkers = others.FindAll(x => x.Id >= marker2.Id);

                //If the owner marker IS part of the nextMarkers, then the bus HAS NOT PASSED yet
                //If the owner marker IS NOT part of the nextMarker, then the bus HAS PASSED earlier
                Markers[busIndex].HasPassed = nextMarkers.Find(x => x.Type == MarkerType.owner) == null;
            }
        }         
        
        /// <summary>
        /// ToString()
        /// This method is the ToString override of a busLine object
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            //Returns the number of the marker as a string
            return Number.ToString();
        }
    }
}
